package com.vise.baseble;

import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;

import com.vise.baseble.callback.IBleCallback;
import com.vise.baseble.callback.IConfigCallback;
import com.vise.baseble.callback.IConnectCallback;
import com.vise.baseble.common.PropertyType;
import com.vise.baseble.core.BluetoothGattChannel;
import com.vise.baseble.core.DeviceMirror;
import com.vise.baseble.core.DeviceMirrorPool;
import com.vise.baseble.exception.BleException;
import com.vise.baseble.model.BluetoothLeDevice;
import com.vise.baseble.utils.HexUtil;
import com.vise.baseble.utils.ViseLog;

import java.util.LinkedList;
import java.util.Queue;
import java.util.UUID;


public class BluetoothDeviceManager {

    private final static String UUID_SERVICE = "0000ffe0-0000-1000-8000-00805f9b34fb";
    private final static String UUID_CHAR_WRITE = "0000ffe1-0000-1000-8000-00805f9b34fb";
    private final static String UUID_CHAR_READ = "0000ffe1-0000-1000-8000-00805f9b34fb";

    private static BluetoothDeviceManager instance;
    private DeviceMirrorPool mDeviceMirrorPool;
    private BluetoothLeDevice currBluetoothLeDevice;


    private boolean deviceOK;


    public boolean isDeviceOK() {
        return deviceOK;
    }

    /**
     * 连接状态回调
     */
    private IConnectCallback currConnectCallback;
    private IConnectCallback connectCallback = new IConnectCallback() {

        @Override
        public void onConnectSuccess(final DeviceMirror deviceMirror) {
            ViseLog.i("Connect Success!");
            if (currConnectCallback != null) {
                currConnectCallback.onConnectSuccess(deviceMirror);
            }
        }

        @Override
        public void onConnectFailure(BleException exception) {
            ViseLog.i("Connect Failure!");
            if (currConnectCallback != null) {
                currConnectCallback.onConnectFailure(exception);
            }
        }

        @Override
        public void onDisconnect(boolean isActive) {
            ViseLog.i("Disconnect!");
            if (currConnectCallback != null) {
                currConnectCallback.onDisconnect(isActive);
            }
        }
    };

    /**
     * 接收数据回调
     */
    private IBleCallback currReceiveDataCallback;
    private IBleCallback receiveCallback = new IBleCallback() {
        @Override
        public void onSuccess(final byte[] data, BluetoothGattChannel bluetoothGattInfo, BluetoothLeDevice bluetoothLeDevice) {
            if (data == null) {
                return;
            }
            ViseLog.i("notify success:" + HexUtil.encodeHexStr(data));

            if (bluetoothLeDevice == currBluetoothLeDevice) {
                if (currReceiveDataCallback != null) {
                    currReceiveDataCallback.onSuccess(data, bluetoothGattInfo, bluetoothLeDevice);
                }
            }
        }

        @Override
        public void onFailure(BleException exception) {
            if (exception == null) {
                return;
            }
            ViseLog.i("notify fail:" + exception.getDescription());

            if (currBluetoothLeDevice != null) {
                if (currReceiveDataCallback != null) {
                    currReceiveDataCallback.onFailure(exception);
                }
            }
        }
    };

    /**
     * 发送数据回调
     */
    private IBleCallback currSendDataCallback;
    private IBleCallback sendBleCallback = new IBleCallback() {
        @Override
        public void onSuccess(final byte[] data, BluetoothGattChannel bluetoothGattInfo, BluetoothLeDevice bluetoothLeDevice) {
            if (data == null) {
                return;
            }
            ViseLog.i("callback success:" + HexUtil.encodeHexStr(data));
            if (currSendDataCallback != null) {
                currSendDataCallback.onSuccess(data, bluetoothGattInfo, bluetoothLeDevice);
            }
        }

        @Override
        public void onFailure(BleException exception) {
            if (exception == null) {
                return;
            }
            ViseLog.i("callback fail:" + exception.getDescription());
            if (currSendDataCallback != null) {
                currSendDataCallback.onFailure(exception);
            }
        }
    };

    //监听收到的数据
    public void enableReceiveData(IBleCallback receiveCallback) {
        currReceiveDataCallback = receiveCallback;
    }

    //监听连接状态
    public void enableConnectCallback(IConnectCallback connectCallback) {
        currConnectCallback = connectCallback;
    }


    private BluetoothDeviceManager() {

    }

    public static BluetoothDeviceManager getInstance() {
        if (instance == null) {
            synchronized (BluetoothDeviceManager.class) {
                if (instance == null) {
                    instance = new BluetoothDeviceManager();
                }
            }
        }
        return instance;
    }

    public void init(Context context) {
        if (context == null) {
            return;
        }
        //蓝牙相关配置修改
        ViseBle.config()
                .setScanTimeout(-1)//扫描超时时间，这里设置为永久扫描
                .setScanRepeatInterval(5 * 1000)//扫描间隔5秒
                .setConnectTimeout(10 * 1000)//连接超时时间
                .setOperateTimeout(5 * 1000)//设置数据操作超时时间
                .setConnectRetryCount(3)//设置连接失败重试次数
                .setConnectRetryInterval(1000)//设置连接失败重试间隔时间
                .setOperateRetryCount(3)//设置数据操作失败重试次数
                .setOperateRetryInterval(1000)//设置数据操作失败重试间隔时间
                .setMaxConnectCount(3);//设置最大连接设备数量
        //蓝牙信息初始化，全局唯一，必须在应用初始化时调用
        ViseBle.getInstance().init(context.getApplicationContext());
        mDeviceMirrorPool = ViseBle.getInstance().getDeviceMirrorPool();
    }

    public void connectByMac(String macAddress) {
        ViseBle.getInstance().connectByMac(macAddress, connectCallback);
    }

    public void disconnect() {
        if (currBluetoothLeDevice != null) {
            ViseBle.getInstance().disconnect(currBluetoothLeDevice);
        }
    }

    public boolean isConnected() {
        if (currBluetoothLeDevice != null) {
            return ViseBle.getInstance().isConnect(currBluetoothLeDevice);
        }
        return false;
    }

    private IConfigCallback currConfigCallback = null;
    public void configDiscoverDevice(BluetoothLeDevice bluetoothLeDevice, IConfigCallback configCallback) {
        if (bluetoothLeDevice != currBluetoothLeDevice) {
            if (currBluetoothLeDevice != null) {
                DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(currBluetoothLeDevice);
                deviceMirror.unregisterNotify(false);
            }

            if (bluetoothLeDevice == null) {
                currBluetoothLeDevice = null;
                currConfigCallback = null;
                return;
            }
        }

        deviceOK = false;
        currConfigCallback = configCallback;
        this.currBluetoothLeDevice = bluetoothLeDevice;
        discoverSeviceCharacter(currBluetoothLeDevice);
    }

    private void discoverSeviceCharacter(BluetoothLeDevice bluetoothLeDevice) {
        bindWriteChannel(bluetoothLeDevice);
        bindReadChannel(bluetoothLeDevice);
    }

    private void bindWriteChannel(BluetoothLeDevice bluetoothLeDevice) {
        DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
        if (deviceMirror != null) {
            BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder()
                    .setBluetoothGatt(deviceMirror.getBluetoothGatt())
                    .setPropertyType(PropertyType.PROPERTY_WRITE)
                    .setServiceUUID(UUID.fromString(UUID_SERVICE))
                    .setCharacteristicUUID(UUID.fromString(UUID_CHAR_WRITE))
                    .setDescriptorUUID(null)
                    .builder();
            deviceMirror.bindChannel(new IBleCallback() {
                @Override
                public void onSuccess(byte[] data, BluetoothGattChannel bluetoothGattChannel, BluetoothLeDevice bluetoothLeDevice) {
                    ViseLog.i("发送数据完成="+HexUtil.encodeHexStr(data));
                }

                @Override
                public void onFailure(BleException exception) {
                }
            }, bluetoothGattChannel);
        }
    }

    private void bindReadChannel(BluetoothLeDevice bluetoothLeDevice) {
        DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
        if (deviceMirror != null) {
            BluetoothGattChannel bluetoothGattChannel = new BluetoothGattChannel.Builder()
                    .setBluetoothGatt(deviceMirror.getBluetoothGatt())
                    .setPropertyType(PropertyType.PROPERTY_NOTIFY)
                    .setServiceUUID(UUID.fromString(UUID_SERVICE))
                    .setCharacteristicUUID(UUID.fromString(UUID_CHAR_READ))
                    .setDescriptorUUID(null)
                    .builder();
            deviceMirror.bindChannel(new IBleCallback() {
                @Override
                public void onSuccess(byte[] data, BluetoothGattChannel bluetoothGattChannel, BluetoothLeDevice bluetoothLeDevice) {
                    DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
                    if (deviceMirror != null) {
                        deviceMirror.setNotifyListener(bluetoothGattChannel.getGattInfoKey(), receiveCallback);
                        new Thread(new Runnable() {
                            @Override
                            public void run() {
                               boolean success = initHardwareDevice();
                               if (currConfigCallback != null) {
                                   currConfigCallback.onConfigComplete(success);
                               }
                            }
                        }).start();
                    }
                }
                @Override
                public void onFailure(BleException exception) {

                }
            }, bluetoothGattChannel);
            registerNotify(bluetoothLeDevice);
        }
    }

    public boolean initHardwareDevice() {
        final String configHex = "434F4E54";
        byte configData[] = HexUtil.decodeHex(configHex);
        byte[] rData = syncWrite(configData, 3000);
        String rHex = HexUtil.encodeHexStr(rData);

        if (rHex != null && rHex.equals("434f4e544f4b")) {
            deviceOK = true;
            ViseLog.i("设备初始化成功,可以正常收发数据" + rHex);
        } else {
            deviceOK = false;
            ViseLog.e("设备初始化失败");
        }
        return deviceOK;
    }

    public void clearHardwareDevice() {
        final String configHex = "4453434E";
        byte configData[] = HexUtil.decodeHex(configHex);
        byte[] rData = syncWrite(configData, 3000);
        String rHex = HexUtil.encodeHexStr(rData);

        if (rHex != null && rHex.equals("4453434e4f4b")) {
            ViseLog.i("硬件设备清理缓存成功" + rHex);
        } else {
            ViseLog.e("硬件设备清理缓存失败");
        }
    }

    public void cardReset() {
        final String configHex = "43525354";
        byte configData[] = HexUtil.decodeHex(configHex);
        byte[] rData = syncWrite(configData, 3000);
        String rHex = HexUtil.encodeHexStr(rData);

        if (rHex != null && rHex.equals("435253544f4b")) {
            ViseLog.i("卡片复位成功" + rHex);
        } else {
            ViseLog.e("卡片复位失败");
        }
    }

    //这里建议时间给长一点，至少给15秒以上，并且不建议在主线程中调用（app卡住十几秒，很可能被kill掉）
    public String getCardNum (long millis) {
        final String configHex = "43474E4D";
        byte configData[] = HexUtil.decodeHex(configHex);
        byte[] rData = syncWrite(configData, millis);
        String rHex = HexUtil.encodeHexStr(rData);
        return rHex;
    }


    public void write(byte[] data) {
        if (currBluetoothLeDevice != null) {
            write(data, null);
        }
    }

    public void write(byte[] data, IBleCallback sendBleCallback) {
        if (currBluetoothLeDevice != null) {
            currSendDataCallback = sendBleCallback;
            write(currBluetoothLeDevice, data);
        }
    }

    private final static Object lockObject = new Object();
    public byte[] syncWrite(byte[] inputData, long millis) {
        final byte[][] rData = {new byte[]{}};
        enableReceiveData(new IBleCallback() {
            @Override
            public void onSuccess(byte[] data, BluetoothGattChannel bluetoothGattChannel, BluetoothLeDevice bluetoothLeDevice) {
                rData[0] = data;
                synchronized (lockObject) {
                    lockObject.notifyAll();
                }
            }
            @Override
            public void onFailure(BleException exception) {
                ViseLog.e("接收数据失败");
                synchronized (lockObject) {
                    lockObject.notifyAll();
                }
            }
        });

        write(inputData);
        synchronized (lockObject) {
            try {
                lockObject.wait(millis);
            } catch (InterruptedException exception) {
                exception.printStackTrace();
            }
        }
        enableReceiveData(null);
        if (rData[0].length == 0) {
            return null;
        } else {
            return rData[0];
        }
    }



    private void write(final BluetoothLeDevice bluetoothLeDevice, byte[] data) {
        if (dataInfoQueue != null) {
            dataInfoQueue.clear();
            dataInfoQueue = splitPacketFor20Byte(data);
            new Thread(new Runnable() {
                @Override
                public void run() {
                    Looper.prepare();
                    new Handler().post(new Runnable() {
                        @Override
                        public void run() {
                            send(bluetoothLeDevice);
                        }
                    });
                    Looper.loop();
                }
            }).start();

        }
    }


    private void registerNotify(BluetoothLeDevice bluetoothLeDevice) {
        DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
        if (deviceMirror != null) {
            deviceMirror.registerNotify(false);
        }
    }

    //发送队列，提供一种简单的处理方式，实际项目场景需要根据需求优化
    private Queue<byte[]> dataInfoQueue = new LinkedList<>();
    private void send(final BluetoothLeDevice bluetoothLeDevice) {
        if (dataInfoQueue != null && !dataInfoQueue.isEmpty()) {
            DeviceMirror deviceMirror = mDeviceMirrorPool.getDeviceMirror(bluetoothLeDevice);
            if (dataInfoQueue.peek() != null && deviceMirror != null) {
                deviceMirror.writeData(dataInfoQueue.poll());
            }
            if (dataInfoQueue.peek() != null) {
                new Handler().postDelayed(new Runnable() {
                    @Override
                    public void run() {
                        send(bluetoothLeDevice);
                    }
                }, 100);
            }
        }
    }

    /**
     * 数据分包
     */
    private Queue<byte[]> splitPacketFor20Byte(byte[] data) {
        Queue<byte[]> dataInfoQueue = new LinkedList<>();
        if (data != null) {
            int index = 0;
            do {
                byte[] surplusData = new byte[data.length - index];
                byte[] currentData;
                System.arraycopy(data, index, surplusData, 0, data.length - index);
                if (surplusData.length <= 20) {
                    currentData = new byte[surplusData.length];
                    System.arraycopy(surplusData, 0, currentData, 0, surplusData.length);
                    index += surplusData.length;
                } else {
                    currentData = new byte[20];
                    System.arraycopy(data, index, currentData, 0, 20);
                    index += 20;
                }
                dataInfoQueue.offer(currentData);
            } while (index < data.length);
        }
        return dataInfoQueue;
    }

}
